package com.lysj.admin.master.common.spring.jpa;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.lysj.admin.master.common.spring.SpringContextUtil;
import com.lysj.admin.master.common.spring.base.BaseEntity;
import com.lysj.admin.master.common.spring.base.BaseService;
import com.lysj.admin.master.common.spring.jpa.tool.LambdaTool;
import com.lysj.admin.master.common.spring.pagination.PageRequestDTO;
import org.springframework.data.domain.Page;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import java.lang.ref.WeakReference;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Created by zk on 2019-12-07 13:31
 * @description jpa链式操作查询
 */
@SuppressWarnings("unchecked")
public class JpaQuery<E extends BaseEntity> {

    private LogicEnum logicEnum = LogicEnum.AND;
    private BaseService<E> baseService;
    private List<MyPredicate> predicateList = new LinkedList<>();
    private Map<Class, WeakReference<BaseService>> cache = new ConcurrentHashMap<>();

    private JpaQuery(Class<E> entityClass) {
        this.baseService = getService(entityClass);
    }

    private JpaQuery(Class<E> entityClass, LogicEnum logicEnum) {
        if (logicEnum != null) {
            this.logicEnum = logicEnum;
        }
        this.baseService = getService(entityClass);
    }

    private JpaQuery(BaseService<E> baseService) {
        this.baseService = baseService;
    }

    private JpaQuery(BaseService<E> baseService, LogicEnum logicEnum) {
        if (logicEnum != null) {
            this.logicEnum = logicEnum;
        }
        this.baseService = baseService;
    }


    public static <E extends BaseEntity> JpaQuery<E> entity(Class<E> entityClass) {
        return new JpaQuery(entityClass);
    }

    public static <E extends BaseEntity> JpaQuery<E> entity(Class<E> entityClass, LogicEnum logicEnum) {
        return new JpaQuery(entityClass, logicEnum);
    }

    /**
     * @author Created by zk on 2019/12/9 10:50
     * @Description 推荐使用传入service进行初始化
     */
    public static <E extends BaseEntity> JpaQuery<E> service(BaseService<E> baseService) {
        return new JpaQuery(baseService);
    }

    public static <E extends BaseEntity> JpaQuery<E> service(BaseService<E> baseService, LogicEnum logicEnum) {
        return new JpaQuery(baseService, logicEnum);
    }


    public JpaQuery<E> eq(FieldFunction<E, ?> fieldFunction, Comparable val) {
        return query(PredicateEnum.EQ, val, fieldFunction);
    }

    public JpaQuery<E> ne(FieldFunction<E, ?> fieldFunction, Comparable val) {
        return query(PredicateEnum.NE, val, fieldFunction);
    }

    public JpaQuery<E> gt(FieldFunction<E, ?> fieldFunction, Comparable val) {
        return query(PredicateEnum.GT, val, fieldFunction);
    }

    public JpaQuery<E> ge(FieldFunction<E, ?> fieldFunction, Comparable val) {
        return query(PredicateEnum.GE, val, fieldFunction);
    }

    public JpaQuery<E> lt(FieldFunction<E, ?> fieldFunction, Comparable val) {
        return query(PredicateEnum.LT, val, fieldFunction);
    }

    public JpaQuery<E> le(FieldFunction<E, ?> fieldFunction, Comparable val) {
        return query(PredicateEnum.LE, val, fieldFunction);
    }

    public JpaQuery<E> fullLike(FieldFunction<E, ?> fieldFunction, Comparable val) {
        return query(PredicateEnum.F_LK, val, fieldFunction);
    }

    public JpaQuery<E> leftLike(FieldFunction<E, ?> fieldFunction, Comparable val) {
        return query(PredicateEnum.L_LK, val, fieldFunction);
    }

    public JpaQuery<E> rightLike(FieldFunction<E, ?> fieldFunction, Comparable val) {
        return query(PredicateEnum.R_LK, val, fieldFunction);
    }

    public JpaQuery<E> in(FieldFunction<E, ?> fieldFunction, Collection val) {
        return query(PredicateEnum.IN, val, fieldFunction);
    }

    public JpaQuery<E> notIn(FieldFunction<E, ?> fieldFunction, Collection val) {
        return query(PredicateEnum.NI, val, fieldFunction);
    }


    public List<E> list() {
        return baseService.findAll(createSpecification());
    }

    public Page<E> pageList(PageRequestDTO pageRequestDTO) {
        return baseService.findAll(createSpecification(), pageRequestDTO.initPage());
    }

    private JpaQuery<E> query(PredicateEnum predicateEnum, Object val, FieldFunction<E, ?> fieldFunction) {
        String property = LambdaTool.getName(fieldFunction);
        MyPredicate myPredicate = new MyPredicate(predicateEnum, val, property);
        predicateList.add(myPredicate);
        return this;
    }

    private Specification<E> createSpecification() {
        Specification<E> specification = (root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicates = new LinkedList<>();
            for (MyPredicate myPredicate : predicateList) {
                Path path = root.get(myPredicate.getProperty());
                PredicateDTO predicateDTO = new PredicateDTO(path, myPredicate.getVal());
                Predicate predicate = myPredicate.getPredicateEnum().getPredicateFunc().apply(predicateDTO, criteriaBuilder);
                predicates.add(predicate);
            }
            if(LogicEnum.OR.equals(logicEnum)){
                return criteriaBuilder.or(ArrayUtil.toArray(predicates, Predicate.class));
            }
            return criteriaBuilder.and(ArrayUtil.toArray(predicates, Predicate.class));
        };
        return specification;
    }

    private BaseService<E> getService(Class<E> entityClass) {
        return Optional.ofNullable(cache.get(entityClass))
                .map(WeakReference::get)
                .orElseGet(() -> {
                    BaseService baseService = (BaseService<E>) SpringContextUtil.getBean(StrUtil.lowerFirst(entityClass.getSimpleName() + "Service"));
                    WeakReference<BaseService> wr = new WeakReference<>(baseService);
                    cache.put(entityClass, wr);
                    return baseService;
                });
    }
}
